// Skinned Mesh
// (c) jimon game studio

#include "CSkinnedMesh.h"

namespace je
{
	namespace scene
	{
		//! Constructor
		CSkinnedMesh::CSkinnedMesh(jeMesh * SkinnedMesh,video::IRender * Render)
			:MeshWithSkeleton(SkinnedMesh),CurrentRender(Render)
		{
			CreateSkinnedArrays();
		}

		//! Destructor
		CSkinnedMesh::~CSkinnedMesh()
		{
			for(u32 i=0;i<SkinnedSurfaces.GetSize();i++)
			{
				SkinnedSurfaces[i]->BindPose->Drop();
				SkinnedSurfaces[i]->Skinned->Drop();
				JEDELETE(SkinnedSurfaces[i])
			}
			SkinnedSurfaces.Clear();
		}

		//! Software Calculate Skin
		void CSkinnedMesh::SoftwareCalculateSkin(u32 AnimationId,f32 Time,u1 EnableInterpolation)
		{
			scene::jeSkeleton * Skeleton = MeshWithSkeleton->GetSkeleton();
			JEASSERT(Skeleton)
			Skeleton->CalculateSkeletonId(AnimationId,Time,EnableInterpolation);

			for(u32 i=0;i<SkinnedSurfaces.GetSize();i++)
			{
				video::IHardwareBufferArray::LayerInfo & PositionLayer_BindPose = SkinnedSurfaces[i]->BindPose->GetLayerInfo(video::HBAL_POSITION);
				video::IHardwareBufferArray::LayerInfo & NormalLayer_BindPose = SkinnedSurfaces[i]->BindPose->GetLayerInfo(video::HBAL_NORMAL);
				video::IHardwareBufferArray::LayerInfo & TangentLayer_BindPose = SkinnedSurfaces[i]->BindPose->GetLayerInfo(video::HBAL_TANGENT);

				video::IHardwareBufferArray::LayerInfo & PositionLayer_Skinned = SkinnedSurfaces[i]->Skinned->GetLayerInfo(video::HBAL_POSITION);
				video::IHardwareBufferArray::LayerInfo & NormalLayer_Skinned = SkinnedSurfaces[i]->Skinned->GetLayerInfo(video::HBAL_NORMAL);
				video::IHardwareBufferArray::LayerInfo & TangentLayer_Skinned = SkinnedSurfaces[i]->Skinned->GetLayerInfo(video::HBAL_TANGENT);

				video::IHardwareBufferArray::LayerInfo & BonesIds = SkinnedSurfaces[i]->BindPose->GetLayerInfo(video::HBAL_BONES_IDS);
				video::IHardwareBufferArray::LayerInfo & BonesW = SkinnedSurfaces[i]->BindPose->GetLayerInfo(video::HBAL_BONES_W);

				u8 * PositionPtr_BindPose = NULL;
				u8 * NormalPtr_BindPose = NULL;
				u8 * TangentPtr_BindPose = NULL;

				u8 * PositionPtr_Skinned = NULL;
				u8 * NormalPtr_Skinned = NULL;
				u8 * TangentPtr_Skinned = NULL;

				u8 * BonesIdsPtr = NULL;
				u8 * BonesWPtr = NULL;

				if(PositionLayer_BindPose.Buffer)
				{
					if(!PositionLayer_BindPose.Buffer->IsLocked())
						PositionLayer_BindPose.Buffer->Lock(core::BLT_READ);
					PositionPtr_BindPose = reinterpret_cast<u8*>(PositionLayer_BindPose.Buffer->GetPtr());
					if(!PositionLayer_Skinned.Buffer->IsLocked())
						PositionLayer_Skinned.Buffer->Lock(core::BLT_READWRITE);
					PositionPtr_Skinned = reinterpret_cast<u8*>(PositionLayer_Skinned.Buffer->GetPtr());
				}
				if(NormalLayer_BindPose.Buffer)
				{
					if(!NormalLayer_BindPose.Buffer->IsLocked())
						NormalLayer_BindPose.Buffer->Lock(core::BLT_READ);
					NormalPtr_BindPose = reinterpret_cast<u8*>(NormalLayer_BindPose.Buffer->GetPtr());
					if(!NormalLayer_Skinned.Buffer->IsLocked())
						NormalLayer_Skinned.Buffer->Lock(core::BLT_READWRITE);
					NormalPtr_Skinned = reinterpret_cast<u8*>(NormalLayer_Skinned.Buffer->GetPtr());
				}
				if(TangentLayer_BindPose.Buffer)
				{
					if(!TangentLayer_BindPose.Buffer->IsLocked())
						TangentLayer_BindPose.Buffer->Lock(core::BLT_READ);
					TangentPtr_BindPose = reinterpret_cast<u8*>(TangentLayer_BindPose.Buffer->GetPtr());
					if(!TangentLayer_Skinned.Buffer->IsLocked())
						TangentLayer_Skinned.Buffer->Lock(core::BLT_READWRITE);
					TangentPtr_Skinned = reinterpret_cast<u8*>(TangentLayer_Skinned.Buffer->GetPtr());
				}

				if(BonesIds.Buffer)
				{
					if(!BonesIds.Buffer->IsLocked())
						BonesIds.Buffer->Lock(core::BLT_READ);
					BonesIdsPtr = reinterpret_cast<u8*>(BonesIds.Buffer->GetPtr());
				}
				if(BonesW.Buffer)
				{
					if(!BonesW.Buffer->IsLocked())
						BonesW.Buffer->Lock(core::BLT_READ);
					BonesWPtr = reinterpret_cast<u8*>(BonesW.Buffer->GetPtr());
				}

				u32 VertCount = PositionLayer_BindPose.Buffer->GetSize() / PositionLayer_BindPose.Stride;

				for(u32 VertIndex = 0; VertIndex < VertCount; VertIndex++)
				{
					jeVector3df * PositionBasePtr_BindPose = NULL;
					jeVector3df * PositionBasePtr_Skinned = NULL;
					jeVector3df * NormalBasePtr_BindPose = NULL;
					jeVector3df * NormalBasePtr_Skinned = NULL;
					jeVector3df * TangentBasePtr_BindPose = NULL;
					jeVector3df * TangentBasePtr_Skinned = NULL;
					u8 * BoneIdsBasePtr = NULL;
					f32 * BoneWBasePtr = NULL;

					if(PositionPtr_BindPose)
					{
						PositionBasePtr_BindPose = reinterpret_cast<jeVector3df*>(PositionPtr_BindPose + PositionLayer_BindPose.Stride * VertIndex + PositionLayer_BindPose.Offset);
						PositionBasePtr_Skinned = reinterpret_cast<jeVector3df*>(PositionPtr_Skinned + PositionLayer_Skinned.Stride * VertIndex + PositionLayer_Skinned.Offset);
					}
					if(NormalPtr_BindPose)
					{
						NormalBasePtr_BindPose = reinterpret_cast<jeVector3df*>(NormalPtr_BindPose + NormalLayer_BindPose.Stride * VertIndex + NormalLayer_BindPose.Offset);
						NormalBasePtr_Skinned = reinterpret_cast<jeVector3df*>(NormalPtr_Skinned + NormalLayer_Skinned.Stride * VertIndex + NormalLayer_Skinned.Offset);
					}
					if(TangentPtr_BindPose)
					{
						TangentBasePtr_BindPose = reinterpret_cast<jeVector3df*>(TangentPtr_BindPose + TangentLayer_BindPose.Stride * VertIndex + TangentLayer_BindPose.Offset);
						TangentBasePtr_Skinned = reinterpret_cast<jeVector3df*>(TangentPtr_Skinned + TangentLayer_Skinned.Stride * VertIndex + TangentLayer_Skinned.Offset);
					}
					if(BonesIdsPtr)
						BoneIdsBasePtr = BonesIdsPtr + BonesIds.Stride * VertIndex + BonesIds.Offset;
					if(BonesWPtr)
						BoneWBasePtr = reinterpret_cast<f32*>(BonesWPtr + BonesW.Stride * VertIndex + BonesW.Offset);

					if(!BoneIdsBasePtr || !BoneWBasePtr)
						break;

					if(PositionBasePtr_Skinned)
						PositionBasePtr_Skinned->SetXYZ(0,0,0);
					if(NormalBasePtr_Skinned)
						NormalBasePtr_Skinned->SetXYZ(0,0,0);
					if(TangentBasePtr_Skinned)
						TangentBasePtr_Skinned->SetXYZ(0,0,0);

					for(u8 BoneCounter = 0;BoneCounter<4;BoneCounter++)
					{
						u8 BoneId = BoneIdsBasePtr[BoneCounter];

						if(!BoneId)
							break;

						jeSkeletonBone * Bone = Skeleton->GetBoneById(BoneId);

						const jeMatrix4f & BoneGlobalMatrix = Bone->GetBoneGlobalMatrix();
						const jeMatrix4f & BoneGlobalNormalMatrix = Bone->GetBoneGlobalNormalMatrix();
						
						if(PositionBasePtr_BindPose && PositionBasePtr_Skinned)
							(*PositionBasePtr_Skinned) += BoneGlobalMatrix.MultiplyMatrixOnVector(*PositionBasePtr_BindPose) * BoneWBasePtr[BoneCounter];
						if(NormalPtr_BindPose && NormalBasePtr_Skinned)
							(*NormalBasePtr_Skinned) += BoneGlobalNormalMatrix.MultiplyMatrixOnVector(*NormalBasePtr_BindPose) * BoneWBasePtr[BoneCounter];
						if(TangentBasePtr_BindPose && TangentBasePtr_Skinned)
							(*TangentBasePtr_Skinned) += BoneGlobalNormalMatrix.MultiplyMatrixOnVector(*TangentBasePtr_BindPose) * BoneWBasePtr[BoneCounter];
					}
				}

				if(PositionLayer_BindPose.Buffer && PositionLayer_BindPose.Buffer->IsLocked())
					PositionLayer_BindPose.Buffer->UnLock();
				if(PositionLayer_Skinned.Buffer && PositionLayer_Skinned.Buffer->IsLocked())
					PositionLayer_Skinned.Buffer->UnLock();

				if(NormalLayer_BindPose.Buffer && NormalLayer_BindPose.Buffer->IsLocked())
					NormalLayer_BindPose.Buffer->UnLock();
				if(NormalLayer_Skinned.Buffer && NormalLayer_Skinned.Buffer->IsLocked())
					NormalLayer_Skinned.Buffer->UnLock();

				if(TangentLayer_BindPose.Buffer && TangentLayer_BindPose.Buffer->IsLocked())
					TangentLayer_BindPose.Buffer->UnLock();
				if(TangentLayer_Skinned.Buffer && TangentLayer_Skinned.Buffer->IsLocked())
					TangentLayer_Skinned.Buffer->UnLock();

				if(BonesIds.Buffer && BonesIds.Buffer->IsLocked())
					BonesIds.Buffer->UnLock();
				if(BonesW.Buffer && BonesW.Buffer->IsLocked())
					BonesW.Buffer->UnLock();
			}

			u32 i=0;
			for(jeMesh::SurfacesListType::Iterator Iter = MeshWithSkeleton->GetSurfaces().begin();Iter != MeshWithSkeleton->GetSurfaces().end();Iter++)
				static_cast<video::jeRenderOperation_Draw*>((*Iter)->GetRenderOperation())->SetArray(SkinnedSurfaces[i++]->Skinned);
		}

		//! Software Restore Arrays
		void CSkinnedMesh::SoftwareRestoreArrays()
		{
			u32 i=0;
			for(jeMesh::SurfacesListType::Iterator Iter = MeshWithSkeleton->GetSurfaces().begin();Iter != MeshWithSkeleton->GetSurfaces().end();Iter++)
				static_cast<video::jeRenderOperation_Draw*>((*Iter)->GetRenderOperation())->SetArray(SkinnedSurfaces[i++]->BindPose);
		}

		//! Buffer Pair
		struct BufferPair
		{
			video::IHardwareBuffer * Original;
			video::IHardwareBuffer * Compare;
		};

		//! Create Skinned Arrays
		void CSkinnedMesh::CreateSkinnedArrays()
		{
			for(jeMesh::SurfacesListType::Iterator Iter = MeshWithSkeleton->GetSurfaces().begin();Iter != MeshWithSkeleton->GetSurfaces().end();Iter++)
			{
				SkinnedSurface * Surface = NULL;
				JENEW(Surface,SkinnedSurface)

				Surface->BindPose = static_cast<video::jeRenderOperation_Draw*>((*Iter)->GetRenderOperation())->Array;
				Surface->BindPose->Grab();

				Surface->Skinned = CurrentRender->CreateHardwareBufferArray();

				jeDynamicArray<BufferPair*> TempBuffers;
				
				for(u32 i = 0;i <= JE_MAX_SHADER_VERTEX_LAYERS;i++)
				{
					video::IHardwareBufferArray::LayerInfo & Info = Surface->BindPose->GetLayerInfo((video::HardwareBufferArrayLayers)i);

					if(Info.Buffer)
					{
						video::IHardwareBuffer * Buffer = NULL;

						for(u32 bi=0;bi<TempBuffers.GetSize();bi++)
							if(TempBuffers[bi]->Original == Info.Buffer)
							{
								Buffer = TempBuffers[bi]->Compare;
								break;
							}

						u1 NowCreated = false;

						if(!Buffer)
						{
							NowCreated = true;

							Buffer = CurrentRender->CreateHardwareBuffer(Info.Buffer->GetDataType());

							Buffer->Lock(core::BLT_WRITE);
							Info.Buffer->Lock(core::BLT_READ);

							Buffer->Allocate(Info.Buffer->GetSize());
							Buffer->WriteFrom(Info.Buffer,Info.Buffer->GetSize());

							Info.Buffer->UnLock();
							Buffer->UnLock();

							BufferPair * Pair = NULL;
							JENEW(Pair,BufferPair)

							Pair->Original = Info.Buffer;
							Pair->Compare = Buffer;

							TempBuffers.InsertElement(Pair);
						}

						Surface->Skinned->AttachBuffer(Buffer,(video::HardwareBufferArrayLayers)i,Info.VariablesPerObject,Info.VariableSize,Info.Stride,Info.Offset,Info.ThisIsIntFormat);

						if(NowCreated)
							Buffer->Drop();
					}
				}

				for(u32 bi=0;bi<TempBuffers.GetSize();bi++)
					JEDELETE(TempBuffers[bi])
				TempBuffers.Clear();

				SkinnedSurfaces.InsertElement(Surface);
			}
		}

	}
}
